#pragma once

#include <map>
#include <vector>
#include <string>
#include "inflate.hpp"

namespace nall { namespace Decode {

struct ZIP {
  struct File {
    std::string name;
    const uint8_t* data;
	uint32_t size;
	uint32_t csize;
	uint32_t cmode;  //0 = uncompressed, 8 = deflate
	uint32_t crc32;
    time_t timestamp;
  };

  ~ZIP() {
  }


  bool open(const uint8_t* data, uint32_t size) {
    if(size < 22) return false;

    filedata = data;
    filesize = size;

	file.clear();// .reset();

    const uint8_t* footer = data + size - 22;
    while(true) {
      if(footer <= data + 22) return false;
      if(read(footer, 4) == 0x06054b50) {
		  uint32_t commentlength = read(footer + 20, 2);
        if(footer + 22 + commentlength == data + size) break;
      }
      footer--;
    }
    const uint8_t* directory = data + read(footer + 16, 4);

    while(true) {
		uint32_t signature = read(directory + 0, 4);
      if(signature != 0x02014b50) break;

      File file;
      file.cmode = read(directory + 10, 2);
      file.crc32 = read(directory + 16, 4);
      file.csize = read(directory + 20, 4);
      file.size  = read(directory + 24, 4);

      uint16_t dosTime = read(directory + 12, 2);
      uint16_t dosDate = read(directory + 14, 2);
      tm info = {};
      info.tm_sec  = (dosTime >>  0 &  31) << 1;
      info.tm_min  = (dosTime >>  5 &  63);
      info.tm_hour = (dosTime >> 11 &  31);
      info.tm_mday = (dosDate >>  0 &  31);
      info.tm_mon  = (dosDate >>  5 &  15) - 1;
      info.tm_year = (dosDate >>  9 & 127) + 80;
      info.tm_isdst = -1;
      file.timestamp = mktime(&info);

	  uint32_t namelength = read(directory + 28, 2);
	  uint32_t extralength = read(directory + 30, 2);
	  uint32_t commentlength = read(directory + 32, 2);

      char* filename = new char[namelength + 1];
      memcpy(filename, directory + 46, namelength);
      filename[namelength] = 0;
      file.name = filename;
      delete[] filename;

	  uint32_t offset = read(directory + 42, 4);
	  uint32_t offsetNL = read(data + offset + 26, 2);
	  uint32_t offsetEL = read(data + offset + 28, 2);
      file.data = data + offset + 30 + offsetNL + offsetEL;

      directory += 46 + namelength + extralength + commentlength;

	  this->file.insert(this->file.end(),file);// .append(file);
    }

    return true;
  }

  vector<uint8_t> extract(File& file) {
    vector<uint8_t> buffer;

    if(file.cmode == 0) {
      buffer.resize(file.size);
      memcpy(&*buffer.begin(), file.data, file.size);
    }

    if(file.cmode == 8) {
      buffer.resize(file.size);
      if(inflate(&*buffer.begin(), buffer.size(), file.data, file.csize) == false) {
        //buffer.reset();
      }
    }

    return buffer;
  }
  
protected:
  const uint8_t* filedata;
  uint32_t filesize;

  uint32_t read(const uint8_t* data, uint32_t size) {
	  uint32_t result = 0, shift = 0;
    while(size--) { result |= *data++ << shift; shift += 8; }
    return result;
  }

public:
  vector<File> file;
};

}}
